package com.htfyun.eink.pw.draw.pen.epd.charcoal;


import com.htfyun.eink.pw.draw.pen.epd.data.NeoRenderPoint;
import com.htfyun.eink.pw.draw.pointer.PWPointer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CharcoalPenImpl {

    private final List<NeoRenderPoint> renderPoints = new ArrayList<>();

    private final Map<Integer, BitmapData> pointTableMap = new HashMap<>();

    public Map<Integer, BitmapData> getPointTableMap() {
        return pointTableMap;
    }

    public List<NeoRenderPoint> getRenderPoints() {
        return renderPoints;
    }

    public NeoRenderPoint createRenderPointerBy(float x, float y) {
        NeoRenderPoint p = new NeoRenderPoint();
        p.bitmapIndex = pointTableMap.size();
        p.x = x;
        p.y = y;
        p.size = 0;
        return p;
    }

    public void strokeTo(CharcoalParam param, PWPointer point) {
        float width = param.configWidth;
        if (param.tiltEnabled) {
            float tiltScale = param.tiltScale;
            float tiltX = point.tiltX;
            float tiltY = point.tiltY;

            width = MathUtils.computeTiltAndOrientation(tiltX, tiltY, tiltScale, width);
        }
        int widthInt = (int) Math.ceil(width);
        initBitmapByPenWidth(param, widthInt);

        PWPointer previousPoint = param.previousPoint;
        if (previousPoint.x == point.x && previousPoint.y == point.y) {
            return;
        }
        float previousX = previousPoint.x;
        float previousY = previousPoint.y;

        while (true) {
            float ratio;
            float distance = MathUtils.distanceBetween(previousX, previousY, point.x, point.y);
            float diff = 3.0F - param.distanceSum;
            if (diff <= 0.0f) {
                param.distanceSum = 0;
                ratio = 0.0f;
            } else {
                if (diff > distance) {
                    param.distanceSum += distance;
                    return;
                }

                ratio = diff / distance;
                param.distanceSum = 0;

                if (ratio < 0.0f) {
                    return;
                }
            }

            param.count++;

            float pressureRatio = (previousPoint.pressure * (1.0F - ratio)) + (ratio * point.pressure);

            int pressure = (int) ((pressureRatio * 17.0F) * 1000.0F * 1000.0F);
            int remainder1000000 = (pressure / 1000) % 1000;
            int remainder1000 = pressure % 1000;
            int paletteWidth = 200 * (remainder1000000 / 200);
            int paletteHeight = 200 * (remainder1000 / 200);

            float pressureRatioSqrt = (float) (Math.abs(Math.sqrt(pressureRatio / 3.0F)) + 0.3F);
            int pressureRatioSqrt10 = (int) (pressureRatioSqrt * 10.0f);
            if (pressureRatio >= 0.9) {
                pressureRatioSqrt10 += 3 * (0x11 * param.count / 3) - 0x11 * param.count;
            }
            float textureRatio = pressureRatioSqrt10 * 0.1F;

            int paletteBmpWidth = param.paletteBitmap.width;
            int paletteBmpHeight = param.paletteBitmap.height;
            int startRow = paletteWidth - paletteWidth / paletteBmpWidth * paletteBmpWidth;
            int startColumn = paletteHeight - paletteHeight / paletteBmpHeight * paletteBmpHeight;

            updateTextureMaskBitmap(param, startColumn, startRow);
            updateCompositeTextureBitmap(param, textureRatio);
            updateCompositeMaskBitmap(param, textureRatio);
//            PrintUtils.printTable(param.mCompositeMaskBmp, "updateCompositeMaskBitmap->mCompositeMaskBmp");
            compositeTextureWithMaskBitmap(param);
            BitmapData compositeTextureBmp = param.mCompositeTextureBmp;

            for (int textureRow = 0; textureRow < compositeTextureBmp.width; textureRow++) {
                for (int textureColumn = 0; textureColumn < compositeTextureBmp.height; textureColumn++) {
                    int alpha = ColorUtils.alphaFromArgb(compositeTextureBmp.data[textureRow][textureColumn]);
                    int rgb32 = ColorUtils.grayscaleToRgb32((0xff & (~alpha)));
                    compositeTextureBmp.data[textureRow][textureColumn] = rgb32;
                }
            }

            ImageUtils.sierraLiteRgb32(param.mCompositeTextureBmp);


            for (int textureRow = 0; textureRow < compositeTextureBmp.width; textureRow++) {
                for (int textureColumn = 0; textureColumn < compositeTextureBmp.height; textureColumn++) {
                    int val = compositeTextureBmp.data[textureRow][textureColumn];
                    compositeTextureBmp.data[textureRow][textureColumn] = val == 0xFFFFFFFF ? 0x0 : param.color;
                }
            }


            previousX = (previousX * (1.0F - ratio)) + (ratio * point.x);
            previousY = (previousY * (1.0F - ratio)) + (ratio * point.y);

            /////////////////////////////

            float halfWidth = width * 0.5f;

            NeoRenderPoint renderPoint = createRenderPointerBy((previousX - halfWidth), (previousY - halfWidth));
            renderPoints.add(renderPoint);
            pointTableMap.put(renderPoint.bitmapIndex, BitmapData.copy(param.mCompositeTextureBmp));

            if (previousX >= point.x && previousY >= point.y) {
                return;
            }
        }
    }

    //生成 mTextureMaskBmp
    public void updateTextureMaskBitmap(CharcoalParam param, int startRow, int startColumn) {

        BitmapData textureMaskBmp = param.mTextureMaskBmp;

        for (int i = 0; i < textureMaskBmp.width; i++) {
            for (int j = 0; j < textureMaskBmp.height; j++) {
                textureMaskBmp.data[i][j] = 0xFFFFFFFF;
            }
        }

        if (textureMaskBmp.height < 1) {
            return;
        }

        BitmapData paletteBitmap = param.paletteBitmap;

        int colorRow = startRow;
        int colorColumn = startColumn;

        for (int row = 0; row < textureMaskBmp.width; row++) {

            for (int column = 0; column < textureMaskBmp.height; column++) {

                if (colorRow >= paletteBitmap.width) {
                    colorRow = 0;
                }
                if (colorColumn >= paletteBitmap.height) {
                    colorColumn = 0;
                }
                int color = (0xff & paletteBitmap.data[colorRow][colorColumn]);
                textureMaskBmp.data[row][column] = color;

                colorColumn++;
            }

            colorRow++;
        }

    }

    //mTextureBmp + mTextureMaskBmp 合成 mCompositeTextureBmp
    public void updateCompositeTextureBitmap(CharcoalParam param, float textureRatio) {
        float value = toCompositeTextureValue(textureRatio);
        float grayscale = value * textureRatio;

        BitmapData textureBmp = param.mTextureBmp;
        BitmapData textureMaskBmp = param.mTextureMaskBmp;
        BitmapData compositeTextureBmp = param.mCompositeTextureBmp;
        for (int row = 0; row < textureBmp.width; row++) {
            for (int column = 0; column < textureBmp.height; column++) {
                int argb = textureBmp.data[row][column];
                int mask = textureMaskBmp.data[row][column];
                int alpha = ColorUtils.alphaFromArgb(argb);
                int result = MathUtils.uint8_multiple((int) (grayscale * alpha), mask);

                compositeTextureBmp.data[row][column] = argb & 0xFFFFFF | (result << 0x18);

            }
        }
    }

    //生成 mCompositeMaskBmp
    public void updateCompositeMaskBitmap(CharcoalParam param, float textureRatio) {
        float value = toCompositeMaskValue(textureRatio);
        float grayscale = value * textureRatio;

        BitmapData compositeMaskBmp = param.mCompositeMaskBmp;

        if (grayscale == 0.0f) {

            for (int row = 0; row < compositeMaskBmp.width; row++) {
                for (int column = 0; column < compositeMaskBmp.height; column++) {
                    compositeMaskBmp.data[row][column] = 0;
                }
            }
            return;
        }

        CompositeMaskBmpHelper.init(compositeMaskBmp);

        for (int row = 0; row < compositeMaskBmp.width; row++) {
            for (int column = 0; column < compositeMaskBmp.height; column++) {
                int argb = compositeMaskBmp.data[row][column];
                int alpha = ColorUtils.alphaFromArgb(argb);
                compositeMaskBmp.data[row][column] = argb & 0xFFFFFF | ((int) (grayscale * alpha) << 0x18);
            }
        }

    }

    //mCompositeTextureBmp+mCompositeMaskBmp->mCompositeTextureBmp
    public void compositeTextureWithMaskBitmap(CharcoalParam param) {
        final double GRAY_SCALE = 0.8f;
        BitmapData mCompositeTextureBmp = param.mCompositeTextureBmp;
        BitmapData compositeMaskBmp = param.mCompositeMaskBmp;

        int widthDiff = mCompositeTextureBmp.width - compositeMaskBmp.width;
        int heightDiff = mCompositeTextureBmp.height - compositeMaskBmp.height;
        if (widthDiff < 0) {
            widthDiff++;
        }
        if (heightDiff < 0) {
            heightDiff++;
        }
        final int rowDistance = widthDiff >> 1;
        final int columnDistance = heightDiff >> 1;

        for (int maskRow = 0; maskRow < compositeMaskBmp.width; maskRow++) {
            int textureRow = maskRow + rowDistance;
            for (int maskColumn = 0; maskColumn < compositeMaskBmp.height; maskColumn++) {
                int textureColumn = columnDistance + maskColumn;

                if (textureRow >= mCompositeTextureBmp.width || textureColumn >= mCompositeTextureBmp.height) {
                    continue;
                }
                int textureArgb = mCompositeTextureBmp.data[textureRow][textureColumn];
                int textureAlpha = ColorUtils.alphaFromArgb(textureArgb);
                if (textureAlpha == 0xFF) {
                    continue;
                }

                int maskArgb = compositeMaskBmp.data[maskRow][maskColumn];
                int maskAlpha = ColorUtils.alphaFromArgb(maskArgb);
                int result = MathUtils.uint8_multiple((int) (maskAlpha * GRAY_SCALE), maskArgb);
                int finalAlpha = result + textureAlpha;
                if (finalAlpha > 0xff) {
                    finalAlpha = 0xff;
                }
                mCompositeTextureBmp.data[textureRow][textureColumn] = textureArgb & 0xFFFFFF | finalAlpha << 0x18;
            }
        }

    }

    private float toCompositeTextureValue(float ratio) {
        float[] values = CompositeTextureValue.getValues();
        if (values.length <= 0) {
            return 0.0F;
        }
        int length = values.length - 1;
        int index = (int) (length * ratio);
        if (index >= 0 && index < values.length) {
            return values[index];
        }
        return values[0];
    }

    private float toCompositeMaskValue(float ratio) {
        float[] values = CompositeMaskValue.getValues();
        if (values.length <= 0) {
            return 0.0F;
        }
        int length = values.length - 1;
        int index = (int) (length * ratio);
        if (index >= 0 && index < values.length) {
            return values[index];
        }
        return values[0];
    }

    public void initBitmapByPenWidth(CharcoalParam param, int widthInt) {
        BitmapData textureBmp = param.mTextureBmp;
        if (textureBmp == null || textureBmp.width != widthInt || textureBmp.height != widthInt) {
//            param.headerInfo = new HeaderInfo(widthInt);

            param.mTextureBmp = new BitmapData(widthInt);
            param.mTextureMaskBmp = new BitmapData(widthInt);
            param.mCompositeTextureBmp = new BitmapData(widthInt);
            param.mCompositeMaskBmp = new BitmapData(widthInt >> 1);

            TextureBmpHelper.init(param.mTextureBmp);


        }
    }

}
